package models

import (
	"errors"
	"fmt"
	"log"
	"strconv"
	"time"
)

type EnvInstance struct {
	ID uint64 `json:"id"`
	Name string `json:"name"`
	CreatedAt time.Time `json:"createdAt"`
	CreatedBy string `json:"createdBy"`

	AccessIP string `json:"accessIP"`
	MainDomain string `json:"mainDomain"`

	JenkinsCreateState string `json:"jenkinsCreateState"`
	NginxCreateState string `json:"nginxCreateState"`
	DBCreateState string `json:"dbCreateState"`
	RedisCreateState string `json:"redisCreateState"`

	CustomerSubDomain string `json:"customerSubDomain"`
	CustomerPort int `json:"customerPort"`
	CustomerGit string `json:"customerGit"`
	CustomerJobName string `json:"customerJobName"`
	CustomerServiceName string `json:"customerServiceName"`
	CustomerDocVolume string `json:"customerDocVolume"`
	CustomerDockerLabel string `json:"customerDockerLabel"`

	AdminSubDomain string `json:"adminSubDomain"`
	AdminPort int `json:"adminPort"`
	AdminJobName string `json:"adminJobName"`
	AdminServiceName string `json:"adminServiceName"`
	AdminGit string `json:"adminGit"`
	AdminDocVolume string `json:"adminDocVolume"`
	AdminDockerLabel string `json:"adminDockerLabel"`

	MobileSubDomain string `json:"mobileSubDomain"`
	MobilePort int `json:"mobilePort"`
	MobileJobName string `json:"mobileJobName"`
	MobileServiceName string `json:"mobileServiceName"`
	MobileGit string `json:"mobileGit"`
	MobileDocVolume string `json:"mobileDocVolume"`
	MobileDockerLabel string `json:"mobileDockerLabel"`

	BackendServiceName string `json:"backendServiceName"`
	BackendJobName string `json:"backendJobName"`
	BackendStaticVolume string `json:"backendStaticVolume"`
	BackendMediaVolume string `json:"backendMediaVolume"`
	BackendGit string `json:"backendGit"`
	CeleryBeatServiceName string `json:"celeryBeatServiceName"`
	CeleryWorkerServiceName string `json:"celeryWorkerServiceName"`
	CeleryJobName string `json:"celeryJobName"`
	BackendDockerLabel string `json:"backendDockerLabel"`

	DBPort int `json:"dbPort"`
	DBName string `json:"dbName"`
	DBUser string `json:"dbUser"`
	DBServiceName string `json:"dbServiceName"`
	DBJobName string `json:"dbJobName"`
	DBDockerLabel string `json:"dbDockerLabel"`

	ApplicationRestartJobName string `json:"applicationRestartJobName"`

	RedisPort int `json:"redisPort"`
	RedisServiceName string `json:"redisServiceName"`
	RedisJobName string `json:"redisJobName"`
	RedisDockerLabel string `json:"redisDockerLabel"`

	DockerNetwork string `json:"dockerNetwork"`
}

type EnvUsedPort struct {
	ID uint64 `json:"id"`
	Port int `json:"port"`
}

func CreateEnvInstance(envInstance *EnvInstance) (uint64, error) {
	// should given Name in envInstance
	if envInstance.Name == "" {
		return  0, errors.New("should given env name when create a new env instance")
	}

	result := db.Create(envInstance)
	if result.Error != nil {
		return 0, result.Error
	}
	if result.RowsAffected != 1 {
		return 0, errors.New("creating env instance, but get 0 affected row")
	}
	return envInstance.ID, nil
}

func UpdateEnvInstance(envInstance EnvInstance) error {
	// should contain ID in envInstance
	if envInstance.ID == 0 {
		return errors.New("should given env instance ID")
	}
	result := db.Save(&envInstance)
	if result.Error != nil {
		return result.Error
	}
	return nil
}

// get node by ID, Name
func GetEnvInstance(getString, getType string) (EnvInstance, error){
	var envInstance EnvInstance
	var err error
	switch getType {
	case "ID":
		var getID int
		getID, err = strconv.Atoi(getString)
		if err != nil {
			log.Println(err)
		} else {
			envInstance = EnvInstance{}
			result := db.First(&envInstance, getID)
			if result.Error != nil {
				log.Println(result.Error)
				err = result.Error
			}
		}
	case "Name":
		envInstance = EnvInstance{}
		result := db.Where("name = ?", getString).First(&envInstance)
		if result.Error != nil {
			log.Println(result.Error)
			err = result.Error
		}
	default:
		err = errors.New(fmt.Sprintf("not supported query type: %s\n", getType))
	}
	return envInstance, err
}

func GetAllEnvInstances() ([]EnvInstance, error) {
	var envInstances []EnvInstance
	result := db.Find(&envInstances)
	if result.Error != nil {
		return nil, result.Error
	}
	return envInstances, nil
}

func WriteCreateState(envName, msg, stateType string) error {
	if msg == "" {
		return errors.New(fmt.Sprintf("message of %s given is blank\n", stateType))
	}
	if len(msg) > 512 {
		log.Println(fmt.Sprintf("length of message of %s is larger than 512, truncated", stateType))
		msg = msg[:512]
	}

	switch stateType {
	case "NginxCreateState":
		result := db.Model(&EnvInstance{}).Where("name = ?", envName).Update("nginx_create_state", msg)
		if result.Error != nil {
			return result.Error
		}
	case "JenkinsCreateState":
		result := db.Model(&EnvInstance{}).Where("name = ?", envName).Update("jenkins_create_state", msg)
		if result.Error != nil {
			return result.Error
		}
	case "DBCreateState":
		result := db.Model(&EnvInstance{}).Where("name = ?", envName).Update("db_create_state", msg)
		if result.Error != nil {
			return result.Error
		}
	case "RedisCreateState":
		result := db.Model(&EnvInstance{}).Where("name = ?", envName).Update("redis_create_state", msg)
		if result.Error != nil {
			return result.Error
		}
	default:
		return errors.New("not supported create state type")
	}
	return nil
}

func GetAllUsedPorts() ([]EnvUsedPort, error) {
	var ports []EnvUsedPort
	result := db.Find(&ports)
	if result.Error != nil {
		log.Print(result.Error)
		return nil, result.Error
	}
	return ports, nil
}

func UpdateUsedPorts( ports []EnvUsedPort ) error {
	result := db.Create(&ports)
	if result.Error != nil {
		log.Println(result.Error)
		return result.Error
	}
	return nil
}

func DeleteUsedPorts(ports []EnvUsedPort) error {
	for _, eachPort := range ports {
		result := db.Where("port = ?", eachPort.Port).Delete(&EnvUsedPort{})
		// log.Printf("affected rows: %d\n", result.RowsAffected)
		if result.Error != nil {
			log.Println(result.Error)
			return result.Error
		}

	}
	return nil
}

func DeleteEnv(id uint64) error {
	env := EnvInstance{ID: id}
	result := db.Delete(&env)
	if result.Error != nil {
		log.Println(result.Error)
		return result.Error
	}
	return nil
}
